package com.jie.scanbarcode

import android.content.Context
import android.net.Uri
import android.util.AttributeSet
import android.util.Size
import android.view.View
import android.widget.RelativeLayout
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.lifecycle.findViewTreeLifecycleOwner
import com.blankj.utilcode.util.LogUtils
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import com.jie.scanbarcode.databinding.CodeViewQrCodeBinding
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.math.min
import kotlin.math.sqrt

/**
 * 二维码扫码组件
 */
class QRCodeView : RelativeLayout {

    private var mCamera : Camera? = null
    private var mImageAnalysis : ImageAnalysis? = null
    private lateinit var mViewBinding : CodeViewQrCodeBinding
    private val mMainExecutor by lazy { ContextCompat.getMainExecutor(context) }
    // 图片分析线程池
    private val mAnalysisExecutor by lazy { Executors.newSingleThreadExecutor() }

    /**
     * 扫描成功回调
     */
    var mSuccessListener: (String) -> Unit = {}

    /**
     * 扫描失败回调
     */
    var mFailureListener: (String) -> Unit = {}

    /**
     * 是否需要显示闪光灯组件，默认显示
     */
    private var isShowLight : Boolean = true

    /**
     * 延时重新开始扫描时间，默认300
     */
    private var mDelayRescan : Int = DELAY_TIME_RESCAN

    /**
     * 是否打开过摄像头，用于页面重新可见时，判断是否需要自动打开摄像头的条件之一
     * true:打开过 false:没打开过
     */
    private var isOpenedCamera : Boolean = false

    /**
     * 是否主动停止图片分析
     * true:调用过 stopAnalysis 方法 false:未调用过，或重新开始分析了，即调用了 startAnalysisTrue 方法
     */
    private var isStopAnalysis : Boolean = false

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        initView(attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        initView(attrs)
    }

    override fun onWindowVisibilityChanged(visibility: Int) {
        super.onWindowVisibilityChanged(visibility)
        LogUtils.i("onWindowVisibilityChanged:$visibility")
        /**
         * 是否需要在页面重新可见时重新打开摄像头或重新开始分析
         * 条件1：页面要可见
         * 条件2：主动调用过startCamera方法，即 isOpenedCamera = true
         * 条件3.1：视频预览useCase未绑定，比如：摄像头由于其他页面被重新绑定，导致当前页面预览被解绑
         * 条件3.2：视频分析useCase已停止，比如：扫码成功后，跳转到其他页面，然后返回当前页面，导致视频分析未重新开始
         */
        val isVisible = visibility == View.VISIBLE
        if (isVisible && isOpenedCamera) {
            val processCameraProvider = getProcessCameraProvider().get()
            if (!processCameraProvider.isBound(mPreviewUseCase)) {
                // 未绑定预览，重新绑定预览和分析useCase
                startCamera()
            } else if (isStopAnalysis) {
                // 视频分析已停止，重新开始分析
                startAnalysisTrue()
            }
        }
        // 当组件不可见时，将闪光灯状态组件置为关闭状态
        if (!isVisible){
            setFlushStatus(false)
        }
    }

    /**
     * 初始化组件
     */
    private fun initView(attrs: AttributeSet?) {
        initAttrs(attrs)
        initLayout(attrs)
        initEvent()
    }

    /**
     * 初始化自定义属性
     */
    private fun initAttrs(attrs: AttributeSet?) {
        attrs?.run {
            val ta =
                context.obtainStyledAttributes(attrs, R.styleable.code_QRCodeView)
            isShowLight = ta.getBoolean(
                R.styleable.code_QRCodeView_code_isShowLight, isShowLight)
            mDelayRescan = ta.getInt(
                R.styleable.code_QRCodeView_code_delay_rescan, mDelayRescan)
            ta.recycle()
        }
    }

    /**
     * 初始化布局
     */
    private fun initLayout(attrs: AttributeSet?) {
        val root = inflate(context, R.layout.code_view_qr_code, this)
        mViewBinding = CodeViewQrCodeBinding.bind(root).apply {
            scanBoxView.init(attrs)
            tvFlushStatus.visibility = if (isShowLight) {
                View.VISIBLE
            } else {
                View.GONE
            }
        }
    }

    /**
     * 初始化组件的事件
     */
    private fun initEvent() {
        mViewBinding.tvFlushStatus.setOnClickListener {
            if (isLightOpen()) {
                closeLight()
            } else {
                openLight()
            }
        }
        val previewViewTouchListener = PreviewViewTouchListener(context)
        previewViewTouchListener.setCustomTouchListener(object : PreviewViewTouchListener.CustomTouchListener{

            override fun zoom(delta: Float) {
                mCamera?.cameraInfo?.zoomState?.value?.let {
                    mCamera?.cameraControl?.setZoomRatio(it.zoomRatio * delta)
                }
            }

            override fun click(x: Float, y: Float) {
                // 触发对焦操作
                mViewBinding.pvCameraX.meteringPointFactory.createPoint(x, y).let {
                    val focusAction = FocusMeteringAction.Builder(it, FocusMeteringAction.FLAG_AF)
                        .setAutoCancelDuration(3, TimeUnit.SECONDS)
                        .build()
                    mCamera?.cameraControl?.startFocusAndMetering(focusAction)
                }
            }

            override fun doubleClick(x: Float, y: Float) {
                mCamera?.cameraInfo?.zoomState?.value?.let {
                    val zoom = if (it.zoomRatio > it.minZoomRatio){
                        0f
                    } else {
                        0.5f
                    }
                    mCamera?.cameraControl?.setLinearZoom(zoom)
                }
            }

            override fun longClick(x: Float, y: Float) {
            }

        })
        mViewBinding.pvCameraX.setOnTouchListener(previewViewTouchListener)
    }

    /**
     * 开始预览并扫码
     */
    fun startCamera() {
        val findViewTreeLifecycleOwner = findViewTreeLifecycleOwner()
        if (findViewTreeLifecycleOwner != null) {
            val processCameraProvider = getProcessCameraProvider()
            processCameraProvider.addListener({
                // 创建预览
                val preview = mPreviewUseCase
                // 设置预览组件
                preview.setSurfaceProvider(mViewBinding.pvCameraX.surfaceProvider)
                // 图片分析接口
                mImageAnalysis = ImageAnalysis.Builder()
                    // 设置输出图片的旋转角度，这个属性经测试无效
//                    .setTargetRotation(Surface.ROTATION_0)
                    // 设置输出图片的分辨率和组件的大小一致
                    .setTargetResolution(Size(measuredWidth, measuredHeight))
                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                    .build()
                startAnalysisTrue()
                // 使用后置摄像头
                val cameraSelector = CameraSelector.Builder()
                    .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                    .build()
                processCameraProvider.get().also {
                    // 先解绑之前的
                    it.unbindAll()
                    // 重新绑定预览useCase，分析useCase
                    mCamera = it.bindToLifecycle(findViewTreeLifecycleOwner, cameraSelector, preview, mImageAnalysis)
                    // 确定已经打开过摄像头了
                    isOpenedCamera = true
                }
            }, mMainExecutor)
        }
    }

    /**
     * 延时重新开始图片分析，防止连续成功但是处理不过来的情况
     */
    fun startAnalysis() {
        postDelayed({
            startAnalysisTrue()
        }, mDelayRescan.toLong())
        // 扫描线直接显示
        mViewBinding.scanBoxView.setShowScanLine(true)
    }

    /**
     * 开始图片分析并处理分析结果
     */
    private fun startAnalysisTrue() {
        val scanRect = mViewBinding.scanBoxView.getScanRect()
        LogUtils.i("viewSize：($measuredWidth, $measuredHeight)")
        // 扫描区域在组件内的坐标
        LogUtils.i("scanRect：$scanRect")
        // 图片分析回调
        val qrCodeAnalysis = QRCodeAnalysis(successListener, failureListener, viewSize = Size(measuredWidth, measuredHeight), analysisRect = scanRect)
        // 先清空，再设置
        mImageAnalysis?.clearAnalyzer()
        mImageAnalysis?.setAnalyzer(mAnalysisExecutor, qrCodeAnalysis)
        mViewBinding.scanBoxView.setShowScanLine(true)
        // 重置为非停止分析状态
        isStopAnalysis = false
    }

    /**
     * 停止图片分析
     */
    private fun stopAnalysis() {
        // 停止图片分析
        mImageAnalysis?.clearAnalyzer()
        // 隐藏扫描线
        mViewBinding.scanBoxView.setShowScanLine(false)
        // 已停止分析
        isStopAnalysis = true
    }

    /**
     * 获取CameraProvider
     */
    private fun getProcessCameraProvider() = ProcessCameraProvider.getInstance(context)

    /**
     * 获取摄像头预览useCase
     */
    private val mPreviewUseCase = Preview.Builder().build()

    private val successListener: (Barcode, Int, Int) -> Unit = { barcode, width, height ->
        // 扫码成功，暂停分析
        stopAnalysis()
        val result =
            "value: ${barcode.rawValue} type:${barcode.valueType} width:$width height:$height"
        LogUtils.i(result)
        // 根据扫码结果缩放摄像头
        zoomPreview(barcode, width, height)
        // 将扫码结果通过回调返回
        if (!barcode.rawValue.isNullOrEmpty()) {
            mSuccessListener.invoke(barcode.rawValue!!)
        } else {
            mFailureListener.invoke("解析结果为空")
        }
    }

    private val failureListener: (Exception) -> Unit = {
        LogUtils.e("解析出错了：$it")
        // 返回扫码失败回调
        mFailureListener.invoke(it.message.toString())
    }

    /**
     * 根据扫码结果缩放摄像头
     */
    private fun zoomPreview(barcode: Barcode, width: Int, height: Int) {
        // 预览图片的较短边长度，即宽度
        val previewMinSize = min(width, height)
        barcode.boundingBox?.let {
            // 计算扫码结果的矩形对角线长度
            val boxWidth = it.width()
            val boxHeight = it.height()
            val boxLen = sqrt((boxWidth * boxWidth + boxHeight * boxHeight).toDouble())
            // 判断是否需要缩放的阈值
            val needZoomSize = previewMinSize / 4
            // 扫码结果矩形框太小，需要缩放
            if (boxLen < needZoomSize) {
                // 计算出缩放值
                val zoom = (needZoomSize - boxLen) / needZoomSize
                // 根据摄像头当前的缩放值，来判断是否需要继续缩放
                mCamera?.cameraInfo?.zoomState?.value?.let { state ->
                    // 限制只能继续放大，不能缩小
                    if (state.linearZoom < zoom) {
                        LogUtils.i("boxLen：$boxLen minZoomSize:$needZoomSize 缩放结果：$zoom")
                        mCamera?.cameraControl?.setLinearZoom(zoom.toFloat())
                    }
                }
            }
        }
    }

    /**
     * 解析本地图片二维码
     */
    fun process(it: Uri) {
        val inputImage : InputImage = InputImage.fromFilePath(context, it)
        BarCodeHelper.process(inputImage, successListener, failureListener)
    }

    /**
     * 获取闪光灯的开启/关闭状态
     * @return true:开启状态 false:关闭状态
     */
    fun isLightOpen() : Boolean {
        // 获取闪光灯的开启/关闭状态
        val status = mCamera?.cameraInfo?.torchState?.value
        return status == TorchState.ON
    }

    /**
     * 开启闪关灯
     */
    fun openLight() {
        // 若闪关灯未打开，则取打开闪关灯
        if (!isLightOpen()) {
            mCamera?.cameraControl?.enableTorch(true)
            // 设置闪光灯为打开状态
            setFlushStatus(true)
        }
    }

    /**
     * 关闭闪光灯
     */
    fun closeLight() {
        // 若闪关灯已打开，则关闭闪关灯
        if (isLightOpen()) {
            mCamera?.cameraControl?.enableTorch(false)
            // 设置闪光灯为关闭状态
            setFlushStatus(false)
        }
    }

    /**
     * 设置闪光灯的打开和关闭状态
     * @param open true:打开状态 false:关闭状态
     */
    private fun setFlushStatus(open: Boolean){
        mViewBinding.tvFlushStatus.run {
            if (visibility == View.VISIBLE){
                isSelected = open
                setText(if (!open) R.string.code_flush_off else R.string.code_flush_on)
            }
        }
    }

    companion object {
        // 延时重新开始扫描
        const val DELAY_TIME_RESCAN = 300
    }

}